package service

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"text/template"

	"github.com/pkg/errors"
	"github.com/redwarn/gotool/httpclient"

	"github.com/sirupsen/logrus"
)

const javaDockerfile = `FROM {{.BaseImage}}
MAINTAINER Tyger.cheng <Tyger.cheng@oriente.com>
COPY build/{{.ProjectName}}.jar  {{.BasePath}}
COPY agent.jar {{.BasePath}}
COPY docker-entrypoint.sh {{.BasePath}}
EXPOSE 8000
ENTRYPOINT ["sh","{{.BasePath}}/docker-entrypoint.sh"]`
const nodeDockerfile = `FROM {{.BaseImage}}
MAINTAINER Tyger.cheng <Tyger.cheng@oriente.com>
EXPOSE 8000`

const htmlDockerfile = `FROM {{.BaseImage}}
MAINTAINER Tyger.cheng <Tyger.cheng@oriente.com>
COPY Caddyfile /etc/Caddyfile
COPY dist {{.BasePath}}/`

const javaDockerEntrypoint = `#!/bin/bash
set -o pipefail
set -o errexit

APP_NAME="{{.ProjectName}}.jar"
APP_PATH="{{.BasePath}}/${APP_NAME}"
APP_PORT=8000
APP_TAG="{{.ImageTag}}"

MAX_DIRECT_SIZE=256 	#heapoff size
META_SIZE=128	 		#metaspace size
MAX_META_SIZE=256 		#max metaspace size

if [[ -z ${MEMORY} ]];then
    echo "Env MEMORY Not Found"
    exit 1
fi

MEMORY_SIZE=$((${MEMORY} / 1024 / 1024))

if [[ ${MEMORY_SIZE} <1600 ]];then
	MAX_DIRECT_SIZE=128
fi

HEAP_SIZE=$((${MEMORY_SIZE} - ${MAX_DIRECT_SIZE} - ${META_SIZE}))
NEW_GEN_SIZE=$((${HEAP_SIZE}  * 3 / 4))

PH_TIMEZONE="Asia/Manila"
ID_TIMEZONE="Asia/Jakarta"
VN_TIMEZONE="Asia/Ho_Chi_Minh"


if [[ -z ${APP_NAME} ]];then
    echo "Please enter a valid application name"
    exit 1
fi

if [[ -z ${APP_ENV} ]];then
    echo "Please enter a valid APP_ENV"
    exit 1
fi

if [[ -z ${REGION_NAME} ]]; then
    TIMEZONE=${PH_TIMEZONE}
elif [[ ${REGION_NAME} == "finizi" ]]; then
    TIMEZONE=${VN_TIMEZONE}
elif [[ ${REGION_NAME} == "cashalo" ]]; then
    TIMEZONE=${PH_TIMEZONE}
elif [[ ${REGION_NAME} == "finmas" ]]; then
    TIMEZONE=${ID_TIMEZONE}
else
	echo $TIMEZONE
fi

if [[ -z ${SW_BUFFER_SIZE} ]];then 
	buffer_size=1000
else
	buffer_size=${SW_BUFFER_SIZE}
fi

if [[ -z ${SW_LOG_LEVEL} ]];then 
	log_level=off
else
	log_level=${SW_LOG_LEVEL}
fi

if [[ -n ${JVMOPTS} ]];then
	JVM_OPTS=${JVMOPTS}
else
	JVM_OPTS="-Xms${HEAP_SIZE}m -Xmx${HEAP_SIZE}m -XX:NewSize=${NEW_GEN_SIZE}m -XX:MaxNewSize=${NEW_GEN_SIZE}m -XX:MetaspaceSize=${META_SIZE}m -XX:MaxMetaspaceSize=${MAX_META_SIZE}m -XX:MaxDirectMemorySize=${MAX_DIRECT_SIZE}m"
fi

if [[ -z ${GCOPTS} ]];then
	GC_OPTS="{{.GcOpts}}"
else
	GC_OPTS=${GCOPTS}
fi

JAVA_OPTS="-server ${JVM_OPTS} ${GC_OPTS}"

SW_OPTS="-javaagent:/data/agent/skywalking-agent.jar -DSW_AGENT_NAME=${REGION_NAME}-${RUN_ENV}-{{.ProjectName}} -DSW_AGENT_NAMESPACE=${namespace} -DSW_AGENT_COLLECTOR_BACKEND_SERVICES=apm.oriente.internal:11800,apm2.oriente.internal:11800 -DSW_LOGGING_LEVEL=${log_level} -Dplugin.springmvc.use_qualified_name_as_endpoint_name=true -Ddictionary.endpoint_name_buffer_size=${buffer_size}"

UNIT_OPTS="-Deureka.instance.metadata-map.tag=${APP_TAG} -Deureka.instance.metadata-map.unit=${UNIT}"

if [[ -z ${SKYWALKING} ]];then
	APM_OPTS=""
else
	APM_OPTS=$SW_OPTS
fi

if [[ ${APP_ENV} == "prod" ]]; then 
	APM_OPTS=$SW_OPTS
fi

IAST="-javaagent:/data/agent.jar -Dproject.name=${REGION_NAME}-${RUN_ENV}-{{.ProjectName}}"

JAVA_RUN="java ${IAST} ${JAVA_OPTS} ${APM_OPTS} ${UNIT_OPTS} -jar -Dspring.profiles.active=${APP_ENV} ${APP_PATH} --server.port=${APP_PORT}"

ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime

exec $JAVA_RUN`

const nodeDockerEntrypoint = `#!/bin/bash

set +o errexit

PH_TIMEZONE="Asia/Manila"
ID_TIMEZONE="Asia/Jakarta"
VN_TIMEZONE="Asia/Ho_Chi_Minh"

if [[ ${APP_ENV} = "prod" ]]; then
    npm run prod-config
elif [[ ${APP_ENV} = "staging" ]]; then
    npm run staging-config
else
    echo "invalid environment!"
    exit 1
fi

if [[ -z ${RUN_ENV} ]]; then 
	RUN_ENV=${APP_ENV}
fi

if [[ -z ${REGION_NAME} ]]; then
    TIMEZONE=${PH_TIMEZONE}
elif [[ ${REGION_NAME} = "finizi" ]]; then
    TIMEZONE=${VN_TIMEZONE}
elif [[ ${REGION_NAME} = "cashalo" ]]; then
    TIMEZONE=${PH_TIMEZONE}
elif [[ ${REGION_NAME} = "finmas" ]]; then
    TIMEZONE=${ID_TIMEZONE}
else
	echo $TIMEZONE
fi

ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime

exec npm start`

const htmlBuild = `#!/bin/bash

build_tag=$(echo $1 | awk -F "_" '{print $1}')
if [[ ${build_tag} == "dev" ]];then
    npm run build:dev
else
    npm run build:prod
fi`
const caddyfile = `0.0.0.0:8000
tls off
gzip
rewrite {
    regexp .*
    to {path} /
}
root /srv
log stdout
errors stdout`

var (
	apiURL = "http://cmdb.oriente.com/api"
	token  = "aadc518ac30e191c934c6ca181c1248546b4a4d3"
)

// Project is cmdb app struct
type Project struct {
	BaseImage     string `json:"base_image"`
	ProjectName   string `json:"project_name,omitempty"`
	ProjectType   string `json:"name"`
	JvmOpts       string `json:"jvm_opts"`
	BasePath      string `json:"run_base_path"`
	ImageTag      string `json:"-"`
	GcOpts        string `json:"gc_opts"`
	CustomJvmOpts string `json:"-"`
}

func getAppMatedata(name, tag string) (*Project, error) {
	var business string
	tags := strings.Split(tag, "_")
	switch tags[1] {
	case "ph":
		business = "cashalo"
	case "vn":
		business = "finizi"
	case "id":
		business = "finmas"
	default:
		business = "arch"
	}
	res := httpclient.Get(fmt.Sprintf("%s/v1/app/?name=%s&business=%s", apiURL, name, business), 30, fmt.Sprintf("Token %s", token))
	if res.StatusCode != http.StatusOK {
		return nil, errors.New(string(res.Body))
	}
	result := &struct {
		Count   int32
		Results []struct {
			Name     string
			JvmOpts  string `json:"jvm_opts"`
			Language *Project
		}
	}{}
	err := json.Unmarshal(res.Body, result)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}
	if result.Count == 0 {
		return nil, errors.New(fmt.Sprintf("application: %s not found", name))
	}
	result.Results[0].Language.ImageTag = tag
	logrus.Printf("MateData info: %v", string(res.Body))
	if result.Results[0].JvmOpts != "" {
		result.Results[0].Language.CustomJvmOpts = result.Results[0].JvmOpts
	}
	return result.Results[0].Language, nil

}
func FileExists(path string) (bool, error) {
	return fileExists(path)
}

func fileExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}
func createFile(path string) (*os.File, error) {
	if ok, _ := fileExists(path); ok {
		_ = os.Remove(path)
	}
	return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
}

func WriteWithIoutil(name, content string) {
	data := []byte(content)
	if ioutil.WriteFile(name, data, 0600) == nil {
		logrus.Println("file write success")
		return
	}
	logrus.Error("file write failure")
}

func GenerateBuildFile(name, tag string) error {
	var dockerfile *template.Template
	var dockerEntryPoint *template.Template
	var output1, output2 io.Writer
	logrus.Printf("application: %s", name)
	project, err := getAppMatedata(name, tag)
	if err != nil {
		logrus.Error("get application error", err)
		return err
	}
	project.ProjectName = name
	Dockerfile, err := os.Create("./Dockerfile")
	if err != nil {
		logrus.Error("create file: ", err)
		return err
	}

	if strings.Contains(project.ProjectType, "java") {
		dockerfile = template.Must(template.New(fmt.Sprintf("%sDockerfile", project.ProjectType)).Parse(javaDockerfile))
		dockerEntryPoint = template.Must(template.New(fmt.Sprintf("%sDockerfile", project.ProjectType)).Parse(javaDockerEntrypoint))
	} else if strings.Contains(project.ProjectType, "node") {
		dockerfile = template.Must(template.New(fmt.Sprintf("%sDockerfile", project.ProjectType)).Parse(nodeDockerfile))
		dockerEntryPoint = template.Must(template.New(fmt.Sprintf("%sDockerfile", project.ProjectType)).Parse(nodeDockerEntrypoint))
	} else if project.ProjectType == "html" {
		dockerfile = template.Must(template.New(fmt.Sprintf("%sDockerfile", project.ProjectType)).Parse(htmlDockerfile))
		dockerEntryPoint = template.Must(template.New(fmt.Sprintf("%sDockerfile", project.ProjectType)).Parse(htmlBuild))

	}
	output1 = Dockerfile
	defer func() {
		if err := recover(); err != nil {
			logrus.Errorf("ci generate scripts failure")
		}
	}()
	if project.ProjectType == "html" {
		if ok, _ := fileExists("./Caddyfile"); !ok {
			WriteWithIoutil("./Caddyfile", caddyfile)
		}

		htmlBuild, err := createFile("./build.sh")
		if err != nil {
			logrus.Error("create file: ", err)
			return err
		}
		output2 = htmlBuild
	} else {
		DockerEntryPoint, err := createFile("./docker-entrypoint.sh")
		if err != nil {
			logrus.Error("create file: ", err)
			return err
		}
		output2 = DockerEntryPoint
	}
	if dockerfile == nil || dockerEntryPoint == nil {
		return errors.New("dockerFile or dockerEntryPoint generate failure")
	}
	err = dockerfile.Execute(output1, project)
	if err != nil {
		logrus.Error("executing template:", err)
		return err
	}
	err = dockerEntryPoint.Execute(output2, project)
	if err != nil {
		logrus.Error("executing template:", err)
		return err
	}
	return nil

}
